// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package jaeger

import (
	"context"
	"encoding/binary"
	"fmt"
	"gitee.com/go-wares/log/base"
	"gitee.com/go-wares/log/conf"
	"gitee.com/go-wares/log/plugins/reporters/jaeger/jaeger"
	"gitee.com/go-wares/log/plugins/reporters/jaeger/thrift"
	"strconv"
	"time"
)

// Formatter
// is a component that convert span as byte array.
type Formatter struct{}

// Format
// log item as string.
func (o *Formatter) Format(spans []base.Span) (body []byte) {
	c := conf.Get()

	b := jaeger.NewBatch()
	b.Process = jaeger.NewProcess()
	b.Process.ServiceName = c.ServiceName
	b.Process.Tags = make([]*jaeger.Tag, 0)
	b.Spans = make([]*jaeger.Span, 0)

	o.GenerateSpans(b, spans)
	o.GenerateProcess(b, c)

	m := thrift.NewTMemoryBuffer()
	if b.Write(context.Background(), thrift.NewTBinaryProtocolConf(m, &thrift.TConfiguration{})) == nil {
		body = m.Buffer.Bytes()
	}
	return
}

// GenerateLog
// generate base item as jaeger log.
func (o *Formatter) GenerateLog(item *base.Item) (log *jaeger.Log) {
	defer item.Release()

	// Prepare
	// jaeger log object.
	log = &jaeger.Log{
		Timestamp: item.Time.UnixMicro(),
		Fields:    make([]*jaeger.Tag, 0),
	}

	// Append
	// normal fields.
	for k, v := range map[string]any{
		"_level":   item.Level.String(),
		"_message": item.String(),
		"_time":    item.Time.Format(time.RFC3339),
	} {
		if g := o.GenerateTag(k, v); g != nil {
			log.Fields = append(log.Fields, g)
		}
	}

	// Append
	// user fields.
	for k, v := range item.Field {
		if g := o.GenerateTag(k, v); g != nil {
			log.Fields = append(log.Fields, g)
		}
	}
	return
}

// GenerateProcess
// generate process profiles of the span.
func (o *Formatter) GenerateProcess(b *jaeger.Batch, c *conf.Config) {
	for k, v := range map[string]any{
		"service.addr":    c.ServiceAddr,
		"service.pid":     c.Pid,
		"service.port":    c.ServicePort,
		"service.name":    c.ServiceName,
		"service.version": c.ServiceVersion,
	} {
		if g := o.GenerateTag(k, v); g != nil {
			b.Process.Tags = append(b.Process.Tags, g)
		}
	}
}

// GenerateSpan
// generate span of a trace.
func (o *Formatter) GenerateSpan(b *jaeger.Batch, item base.Span) {
	// Prepare
	// span object.
	s := &jaeger.Span{
		Duration:      item.Duration().Microseconds(),
		Flags:         1,
		OperationName: item.Name(),
		StartTime:     item.StartTime().UnixMicro(),
	}

	// Generate
	// trace id and span id.
	s.TraceIdHigh = int64(binary.BigEndian.Uint64(item.Tracing().TraceId.Byte()[0:8]))
	s.TraceIdLow = int64(binary.BigEndian.Uint64(item.Tracing().TraceId.Byte()[8:16]))
	s.SpanId = int64(binary.BigEndian.Uint64(item.Tracing().SpanId.Byte()))

	// Generate
	// parent span id.
	if v := item.Tracing().ParentSpanId; v != nil {
		s.ParentSpanId = int64(binary.BigEndian.Uint64(v.Byte()))
	}

	// Generate tags.
	if x := item.GetAttr(); x != nil {
		s.Tags = make([]*jaeger.Tag, 0)

		for k, v := range x {
			if g := o.GenerateTag(k, v); g != nil {
				s.Tags = append(s.Tags, g)
			}
		}
	}

	// Generate logs.
	if logs := item.Logs(); logs != nil {
		s.Logs = make([]*jaeger.Log, 0)
		for _, x := range logs {
			if g := o.GenerateLog(x); g != nil {
				s.Logs = append(s.Logs, g)
			}
		}
	}

	// Append span of a trace.
	b.Spans = append(b.Spans, s)
}

// GenerateSpans
// generate span of a trace.
func (o *Formatter) GenerateSpans(b *jaeger.Batch, spans []base.Span) {
	for _, item := range spans {
		o.GenerateSpan(b, item)
	}
}

// GenerateTag
// generate key/value as jaeger tag.
func (o *Formatter) GenerateTag(key string, value any) (tag *jaeger.Tag) {
	defer func() {
		if tag != nil {
			tag.Key = key
		}
	}()

	switch value.(type) {
	case bool:
		v, _ := value.(bool)

		tag = jaeger.NewTag()
		tag.VType = jaeger.TagType_BOOL
		tag.VBool = &v
	case float32, float64:
		v, _ := strconv.ParseFloat(fmt.Sprintf("%v", value), 64)

		tag = jaeger.NewTag()
		tag.VType = jaeger.TagType_DOUBLE
		tag.VDouble = &v
	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
		v, _ := strconv.ParseInt(fmt.Sprintf("%v", value), 10, 64)

		tag = jaeger.NewTag()
		tag.VType = jaeger.TagType_LONG
		tag.VLong = &v
	case string:
		v := value.(string)

		tag = jaeger.NewTag()
		tag.VType = jaeger.TagType_STRING
		tag.VStr = &v
	default:
		v := fmt.Sprintf("%v", value)

		tag = jaeger.NewTag()
		tag.VType = jaeger.TagType_STRING
		tag.VStr = &v
	}
	return
}
